﻿
using BoilenEditor.Primitives;
using BoilenEditor.Primitives.Snippets;
using BoilenEditor.Properties;
using System;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows;
using System.Windows.Input;


namespace BoilenEditor {

    public partial class MainWindow : Window {

        private static readonly Regex AssignsExternalDocumentationPrefix = new Regex( @"GlobalSettings.ExternalDocumentationPrefix\s*=\s*""", RegexOptions.Compiled );

        private readonly TemplateFileData templateData_ = new TemplateFileData( );

        private IAsyncResult toolSearch_;
        private Mutex mutex_;   // Used to prevent accidental simultaneous startup of multiple instances with the same arguments.


        public MainWindow( ) {
            InitializeComponent( );

            // Check that we are the only instance that has been created with these arguments.
            if( this.IsDuplicateInstance( ) )
                this.Close( );

            // Check default settings.
            string textTransformPath = Settings.Default.TextTransformPath;
            if( !File.Exists( textTransformPath ) ) {
                Action worker = MainWindow.UpdateTextTransformTool;
                this.toolSearch_ = worker.BeginInvoke( worker.EndInvoke, null );
            }
            else {
                this.toolSearch_ = null;
            }

            this.Loaded += WindowLoaded;
        }

        private void WindowLoaded( object sender, RoutedEventArgs e ) {
            this.Loaded -= WindowLoaded;

            this.Hide( );

            bool success;
            string firstArg = App.Arguments.FirstOrDefault( );

            // If the "-all" command line argument was specified, transform all template files in the solution directory.
            if( firstArg.OrdinalEqual( "-all" ) ) {
                success = false;

                string filter = App.Arguments.ElementAtOrDefault( 1 );
                OpenData openData = OpenData.Create( filter );
                Console.WriteLine( "Converting all .b.tt files in " + openData.SolutionDirectory );

                foreach( ProjectData project in openData.Projects ) {
                    Console.WriteLine( "  Project: " + project.DisplayName );
                    foreach( FileData file in project.TemplateFiles ) {
                        Console.WriteLine( "    File: " + file.FilePath );

                        this.templateData_.SourceFile = file;
                        if( !this.Save( ) ) {
                            Console.WriteLine( "      Failure:" );
                            Console.WriteLine( this.templateData_.CurrentStatusText );
                        }
                    }
                }

                Console.WriteLine( "Done" );
                Console.WriteLine( );
            }
            else {
                // If file specified as a command line argument, use it.
                FileData overrideFile = FindOverrideFile( firstArg );
                if( overrideFile.HasValue( ) ) {
                    this.templateData_.SourceFile = overrideFile;
                    success = true;
                }
                // Otherwise, show file open window.
                else {
                    success = ShowOpenWindow( null );
                }
            }

            if( success ) {
                this.DataContext = this.templateData_;
                this.ShowInTaskbar = true;
                this.CloseMutex( );
                this.Show( );
            }
            else {
                this.Close( );
            }
        }


        protected override void OnClosing( CancelEventArgs e ) {
            base.OnClosing( e );

            if( !this.ConfirmedSave( ) )
                e.Cancel = true;
        }

        protected override void OnClosed( EventArgs e ) {
            base.OnClosed( e );

            this.CloseMutex( );
        }

        private bool IsDuplicateInstance( ) {
            string escapedArguments = string.Join( " ", App.Arguments ).Replace( Path.DirectorySeparatorChar, '|' ).Replace( Path.AltDirectorySeparatorChar, '|' );
            string name = "BoilenEditor; " + escapedArguments;

            bool createdNew;
            this.mutex_ = new Mutex( false, name, out createdNew );

            return !createdNew;
        }

        private void CloseMutex( ) {
            using( this.mutex_ )
                this.mutex_ = null;
        }


        private OpenData openData_;
        private bool ShowOpenWindow( MainWindow owner ) {
            if( this.openData_ == null )
                this.openData_ = OpenData.Create( Settings.Default.ProjectDirectoryFilter );

            OpenWindow openWindow = new OpenWindow( owner, this.openData_ );
            bool? result = openWindow.ShowDialog( );
            bool success = result.GetValueOrDefault( );

            if( success ) {
                FileData file = openWindow.SelectedFile;
                this.templateData_.SourceFile = file;
            }

            return success;
        }

        private bool ShowPropertiesWindow( ) {
            SettingsWindow propertiesWindow = new SettingsWindow( this );
            bool? result = propertiesWindow.ShowDialog( );
            bool success = result.GetValueOrDefault( );

            if( success ) {
                WindowSettings ws = Settings.Default.MainWindowSettings;
                ws.Apply( this );
            }

            return success;
        }

        private bool ShowSnippetWindow( ) {
            bool showDescription;
            var includesReference = this.templateData_.SourceFileContents.GetReferences( )
                .FirstOrDefault( path => Path.GetFileName( path ).OrdinalEqual( "Includes.tt" ) );
            if( includesReference == null ) {
                showDescription = true;
            }
            else {
                string fileDirectory = Path.GetDirectoryName( this.templateData_.SourceFile.FilePath );
                string includesPath = Path.GetFullPath( Path.Combine( fileDirectory, includesReference ) );
                string content = File.ReadAllText( includesPath );
                showDescription = !AssignsExternalDocumentationPrefix.IsMatch( content );
            }

            SnippetWindow snippetWindow = new SnippetWindow( this, showDescription );
            bool? result = snippetWindow.ShowDialog( );
            bool success = result.GetValueOrDefault( );

            if( success ) {
                DeclarationData declaration = snippetWindow.Declaration;
                string declarationText = declaration.GetDeclarationText( showDescription );
                InsertBehavior.Insert( this.templateBox_, declarationText );
            }

            return success;
        }


        private SearchWindow searchWindow_;
        private SearchWindow SearchWindow {
            get {
                if( this.searchWindow_ == null ) {
                    this.searchWindow_ = new SearchWindow( this );
                    this.searchWindow_.SearchDataChanged += delegate { this.Search( null ); };
                }

                return this.searchWindow_;
            }
        }

        private void ShowSearchWindow( bool replace ) {
            SearchWindow searchWindow = this.SearchWindow;
            searchWindow.ShowReplace = replace;
            searchWindow.SelectedText = this.templateBox_.SelectedText;
            searchWindow.Show( );
            searchWindow.Focus( );
        }

        private void Search( bool? findNext ) {
            if( this.searchWindow_ != null ) {
                SearchData search = this.searchWindow_.SearchData;
                SearchBehavior.Search( this.SearchWindow, this.templateBox_, search, findNext );
            }
        }


        private bool ShouldSave {
            get {
                FileContents contents = this.templateData_.SourceFileContents;
                return contents.HasValue( )
                    && contents.IsModified;
            }
        }

        private bool ConfirmedSave( ) {
            if( this.ShouldSave ) {
                string message = "Transform and save changes to \"{0}\"?".With( this.templateData_.SourceFile.Name );
                MessageBoxResult result = MessageBox.Show( this, message, "BoilenEditor", MessageBoxButton.YesNoCancel, MessageBoxImage.Question );

                switch( result ) {
                    case MessageBoxResult.Cancel:
                        return false;
                    case MessageBoxResult.Yes:
                        return this.Save( );
                }
            }

            return true;
        }

        private bool Save( bool forceTransform = false ) {
            if( this.toolSearch_.HasValue( ) ) {
                this.toolSearch_.AsyncWaitHandle.WaitOne( );
                this.toolSearch_ = null;
            }

            if( !File.Exists( Settings.Default.TextTransformPath ) ) {
                MessageBox.Show( this, "The path to the TextTransform.exe tool could not be found automatically. Please set it manually.", "BoilenEditor.Save", MessageBoxButton.OK, MessageBoxImage.Warning );
                return false;
            }

            return this.templateData_.Save( forceTransform );
        }


        private void OpenExecuted( object sender, ExecutedRoutedEventArgs e ) {
            if( this.ConfirmedSave( ) )
                this.ShowOpenWindow( this );
        }

        private void SaveCanExecute( object sender, CanExecuteRoutedEventArgs e ) {
            if( this.ShouldSave )
                e.CanExecute = true;
        }

        private void SaveExecuted( object sender, ExecutedRoutedEventArgs e ) {
            this.Save( e.Command != ApplicationCommands.Save );
        }

        private void PropertiesExecuted( object sender, ExecutedRoutedEventArgs e ) {
            this.ShowPropertiesWindow( );
        }

        private void CloseExecuted( object sender, ExecutedRoutedEventArgs e ) {
            this.Close( );
        }

        private void FindExecuted( object sender, ExecutedRoutedEventArgs e ) {
            this.ShowSearchWindow( false );
        }

        private void ReplaceExecuted( object sender, ExecutedRoutedEventArgs e ) {
            this.ShowSearchWindow( true );
        }

        private void QuickFindExecuted( object sender, ExecutedRoutedEventArgs e ) {
            bool searchNext = e.Parameter.ToString( ) == "Forward";
            this.Search( searchNext );
        }

        private void HelpExecuted( object sender, ExecutedRoutedEventArgs e ) {
            this.ShowSnippetWindow( );
        }


        private static void UpdateTextTransformTool( ) {
            const string TextTransformName = "TextTransform.exe";
            string programFilesPath =
                   Environment.GetEnvironmentVariable( "ProgramFiles(x86)" )
                ?? Environment.GetEnvironmentVariable( "ProgramFiles" );
            Settings.Default.TextTransformPath = "Searching for {0} under {1}".With( TextTransformName, programFilesPath );

            string[] files = Ex.GetFiles( programFilesPath, TextTransformName ).ToArray( );
            Array.Sort( files );

            string path = files.LastOrDefault( );
            Settings.Default.TextTransformPath =
                path
                ?? "Error: Could not find {0} under {1}".With( TextTransformName, programFilesPath );

            Settings.Default.Save( );
        }

        private static FileData FindOverrideFile( string filePath ) {
            FileData overrideFile = null;

            if( File.Exists( filePath ) ) {
                // Find directory the project and solution file falls under.
                string fullPath = Path.GetFullPath( filePath );
                string projectPath = Ex.GetParentDirectories( fullPath )
                    .SelectMany( directory => Ex.GetFiles( directory, "*.csproj", SearchOption.TopDirectoryOnly ) )
                    .FirstOrDefault( path => !path.Contains( ".SL" ) );
                string solutionDirectory = Ex.GetParentDirectories( projectPath ?? fullPath )
                    .FirstOrDefault( directory => Ex.TestFile( directory, "*.sln" ) );

                if( !string.IsNullOrEmpty( projectPath ) && !string.IsNullOrEmpty( solutionDirectory ) ) {
                    // Search for file among solution's project files.
                    ProjectData projectData = new ProjectData( projectPath );
                    overrideFile =
                        projectData.TemplateFiles
                            .SingleOrDefault( file => file.FilePath.OrdinalEqual( fullPath ) );

                    // If file was found, update current directory to match solution directory.
                    if( overrideFile.HasValue( ) )
                        Environment.CurrentDirectory = solutionDirectory;
                }
            }

            return overrideFile;
        }

    }

}
